package com.vvv.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vvv.shortlink.admin.dao.entity.UserDO;
import com.vvv.shortlink.admin.dao.mapper.UserMapper;
import com.vvv.shortlink.common.dto.UserInfoDTO;
import com.vvv.shortlink.admin.dto.req.ShortLinkGroupSaveReqDTO;
import com.vvv.shortlink.admin.dto.req.UserLoginReqDTO;
import com.vvv.shortlink.admin.dto.req.UserRegisterReqDTO;
import com.vvv.shortlink.admin.dto.req.UserUpdateReqDTO;
import com.vvv.shortlink.admin.dto.resp.ActualUserRespDTO;
import com.vvv.shortlink.admin.dto.resp.UserLoginRespDTO;
import com.vvv.shortlink.admin.dto.resp.UserRespDTO;
import com.vvv.shortlink.admin.service.GroupService;
import com.vvv.shortlink.admin.service.UserService;
import com.vvv.shortlink.common.convention.excepton.ClientException;
import com.vvv.shortlink.common.enums.UserErrorCodeEnum;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.vvv.shortlink.admin.constant.RedisConstant.*;
import static com.vvv.shortlink.common.convention.errorcode.BaseErrorCode.USER_REGISTER_ERROR;
import static com.vvv.shortlink.common.enums.UserErrorCodeEnum.*;

/**
 * @author vvv
 * @description 针对表【t_user】的数据库操作Service实现
 * @createDate 2024-01-28 17:26:00
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO>
        implements UserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate stringRedisTemplate;

    private final GroupService groupService;

    @Override
    public UserRespDTO getUserByUsername(String username) {
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username);
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        if (userDO == null) {
            throw new ClientException(UserErrorCodeEnum.USER_IS_NULL);
        }
        return BeanUtil.toBean(userDO, UserRespDTO.class);
    }

    @Override
    public ActualUserRespDTO getActualUserByUsername(String username) {
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username);
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        if (userDO == null) {
            throw new ClientException(UserErrorCodeEnum.USER_IS_NULL);
        }
        return BeanUtil.toBean(userDO, ActualUserRespDTO.class);
    }

    @Override
    public Boolean hasUsername(String username) {
        return userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Override
    public void register(UserRegisterReqDTO requestParam) {
        String username = requestParam.getUsername();
        String password = requestParam.getPassword();
        // 1. 参数检验
        if (StringUtils.isEmpty(username)) {
            throw new ClientException(USER_NAME_IS_NULL);
        }
        if (StringUtils.isEmpty(password)) {
            throw new ClientException(USER_PASSWORD_IS_NULL);
        }
        if (this.hasUsername(username)) {
            throw new ClientException(USER_NAME_EXISTED);
        }
        // 2. 并发控制
        RLock lock = redissonClient.getLock(LOCK_REGISTER_USERNAME_KEY + username);
        boolean isLock = lock.tryLock();
        if (!isLock) {
            throw new ClientException(CANNOT_REGISTER_REPEATEDLY);
        }
        try {
            if (checkLogin(username)) {
                throw new ClientException("该用户名已经被注册了");
            }
            UserDO userDO = new UserDO();
            BeanUtils.copyProperties(requestParam, userDO);
            int insert = baseMapper.insert(userDO);
            if (insert < 1) {
                throw new ClientException(USER_REGISTER_ERROR);
            }
            // 3. username 写入布隆过滤器
            userRegisterCachePenetrationBloomFilter.add(username);
            // 4. 创建默认短链分组
            groupService.saveGroup(userDO.getId(), new ShortLinkGroupSaveReqDTO("默认分组"));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void update(UserUpdateReqDTO requestParam) {
        LambdaUpdateWrapper<UserDO> updateWrapper = Wrappers.lambdaUpdate(UserDO.class)
                .eq(UserDO::getUsername, requestParam.getUsername());
        baseMapper.update(BeanUtil.toBean(requestParam, UserDO.class), updateWrapper);
    }

    /**
     * 这里想的是如果用户登录成功就返回一个token作为凭证，并在redis中记录用户登录信息
     * 同时还有一个接口是通过拿请求头里面的token去redis判断用户是否登录
     * 但是这个接口要做防刷，也就是登录过了的用户不能重复登录
     */
    @Override
    public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
        String username = requestParam.getUsername();
        String password = requestParam.getPassword();
        if (StringUtils.isAnyEmpty(username, password)) {
            throw new ClientException("用户名和密码不能为空");
        }
        // 1. 判断用户是否已经登录
        if (checkLogin(username)) {
            /**
             * 这里可以选择抛异常不让用户重复登录，防止生成的token撑爆redis
             * 也可以直接返回防刷key的token，直接让用户登录
             */
            //throw new ClientException("不能重复登录");
            String isLoginKey = LOGIN_IS_LOGIN_KEY + username;
            return new UserLoginRespDTO(stringRedisTemplate.opsForValue().get(isLoginKey));
        }
        // 2. 用户名密码校验
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, username)
                .eq(UserDO::getPassword, password);
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        if (userDO == null) {
            throw new ClientException("用户名或密码错误");
        }
        // 3. 保存用户状态到redis
        String token = IdUtil.simpleUUID();
        // token 为 key 存用户登陆凭证
        UserInfoDTO userInfoDTO = BeanUtil.toBean(userDO, UserInfoDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userInfoDTO, new HashMap<>()
                , CopyOptions.create()
                        .setIgnoreNullValue(false)
                        .setFieldValueEditor((fieldKey, fieldValue) -> fieldValue != null ? fieldValue.toString() : null));
        String tokenKey = LOGIN_USERINFO_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey,LOGIN_TTL, TimeUnit.SECONDS);
        // 防刷标识
        String isLoginKey = LOGIN_IS_LOGIN_KEY + username;
        stringRedisTemplate.opsForValue().set(isLoginKey, token, LOGIN_TTL, TimeUnit.SECONDS);
        return new UserLoginRespDTO(token);
    }

    @Override
    public Boolean checkLogin(String username) {
        return stringRedisTemplate.hasKey(LOGIN_IS_LOGIN_KEY + username);
    }

    @Override
    public void logout(String token, String username) {
        if (!this.checkLogin(token)) {
            throw new ClientException("请登录");
        }
        stringRedisTemplate.delete(LOGIN_USERINFO_KEY + token);
        stringRedisTemplate.delete(LOGIN_IS_LOGIN_KEY + username);
    }
}




